#ifndef __SHMIPC_COM_XMESSAGE_H__
#define __SHMIPC_COM_XMESSAGE_H__

#include <cstdint>
#include <memory>
#include <vector>
#include <cstring>

#include "shmipc/com/com_types.h"
#include "shmipc/core/xqueue.h"

#define XMSG_START_CODE 0xabcd
#define XMSG_START_CODE_BYTE1 0xcd
#define XMSG_START_CODE_BYTE2 0xab

#define XMSG_PROTOCOL_VER (1)
/**unkown message type */
#define XMSG_UNKNOWN_TYPE (0)
#define XMSG_GET_PROTOCOL_VER(flags) ((flags) & 0x0f)

#define XMSG_FLAGS_TP_FLAG_MASK (0x80)
#define XMSG_FLAGS_MASK (0xf0)

#define XMSG_FLAGS_TP_FLAG(flags) ((flags) & XMSG_FLAGS_TP_FLAG_MASK)
#define XMSG_FLAGS(flags) ((flags) & XMSG_FLAGS_MASK)

#define XMSG_FLAGS_TP_FLAG_ENABLE(flags)  do{flags |= XMSG_FLAGS_TP_FLAG_MASK;}while(0)
#define XMSG_FLAGS_TP_FLAG_DISABLE(flags) do{flags &= (~XMSG_FLAGS_TP_FLAG_MASK);}while(0)


/**for base header */
#define XMSG_FIELD_OFFSET_LENGTH (0)
#define XMSG_FIELD_OFFSET_STARTCODE (4)
#define XMSG_FIELD_OFFSET_FLAGS (6)
#define XMSG_FIELD_OFFSET_TYPE (7)      /**< Message type */
#define XMSG_FIELD_OFFSET_MSG_ID (8)    /**< Message identify */
#define XMSG_FIELD_OFFSET_SEQ_NUM (10)  /**< sequence number */
#define XMSG_BASE_HDR_SIZE (12)

/** for extension header about communication protocol(unpacking/pacakging) */
#define XMSG_FIELD_OFFSET_PKT_TOTAL (XMSG_BASE_HDR_SIZE)
#define XMSG_FIELD_OFFSET_PKT_IDX   (XMSG_FIELD_OFFSET_PKT_TOTAL + 4)
#define XMSG_PKT_HDR_SIZE (8)

#define XMSG_PKT_IDX_START (1)
#define XMSG_PKT_IDX_INVALID (0)

namespace shmipc::com {

enum XMessageType: uint8_t {
    XMsg_TypeNone = 0,
    /** 1 ~ 0x30:  is private message type for specifical transport protocol */

    XMsg_TypePrivateMax               = 0x30, /** private message type maxim for specifical transport protocol */ 
    /** public message type define */
    XMsg_TypeMethodRequest            = 0x71, /** (client --> service) the payload is the parameter of request (need response @see XMsg_TypeMethodResponse) */
    XMsg_TypeMethodRequestNoReturn    = 0x72, /** (client --> service) the payload is the parameter of request (needn't response)  */
    XMsg_TypeMethodResponse           = 0x73, /** (service --> client) the payload is the response data of request (needn't response)  */

    XMsg_TypeEvent                    = 0x74, /** (service --> client) the payload is the event content (needn't response)  */
    XMsg_TypeEventSubscribe           = 0x75, /** (client --> service) no payload, just message header only (need response @see XMsg_TypeEventSubscribeAck) */
    XMsg_TypeEventSubscribeGroup      = 0x76, /** (client --> service) the payload is a array of event id(message id must be aways 0). (need response @see XMsg_TypeEventSubscribeAck) */
    XMsg_TypeEventSubscribeAck        = 0x77, /** (service --> client) no payload, have return code, @see XMessageReturnCode (needn't response) */
    XMsg_TypeEventStopSubscribe       = 0x78, /** (client --> service) no payload, just message header only (need response @see XMsg_TypeEventStopSubscribeAck)*/
    XMsg_TypeEventStopSubscribeGroup  = 0x79, /** (client --> service) the payload is a array of event id(message id must be aways 0). (need response @see XMsg_TypeEventStopSubscribeAck) */
    XMsg_TypeEventStopSubscribeAck    = 0x7a, /** (service --> client) no payload, have return code, @see XMessageReturnCode (needn't response) */

    XMsg_TypePropertySet              = 0x7b, /** (client --> service) the payload is the parameter of request (need response @see XMsg_TypePropertySetAck) */
    XMsg_TypePropertySetAck           = 0x7c, /** (service --> client) the payload is the response data of request, have return code, @see XMessageReturnCode (needn't response)  */
    XMsg_TypePropertyGet              = 0x7d, /** (client --> service) the payload is the parameter of request (need response @see XMsg_TypePropertyGet_Ack) */
    XMsg_TypePropertyGetAck           = 0x7e, /** (service --> client) the payload is the response data of request, have return code, @see XMessageReturnCode  (needn't response)  */
    XMsg_TypePropertyNotification     = 0x7f, /** (service --> client) the payload is the value content of property (needn't response)  */

    XMsg_TypePropertySubscribe        = 0x80, /** (client --> service) no payload, just message header only (need response @see XMsg_TypePropertySubscribe_Ack) */
    XMsg_TypePropertySubscribeAck     = 0x81, /** (service --> client) no payload, have return code, @see XMessageReturnCode (needn't response) */
    XMsg_TypePropertyStopSubscribe    = 0x82, /** (client --> service) no payload, just message header only (need response @see XMsg_TypePropertyStop_Subscribe_Ack) */
    XMsg_TypePropertyStopSubscribeAck = 0x83, /** (service --> client) no payload, have return code, @see XMessageReturnCode (needn't response) */
};

namespace XMessageHelper {
static inline uint32_t ByteRead4(const uint8_t* pdata) { return pdata[0] | (pdata[1] << 8) | (pdata[2] << 16) | (pdata[3] << 24); }
static inline uint16_t ByteRead2(const uint8_t* pdata) { return pdata[0] | (pdata[1] << 8); }

static inline void ByteWrite2(uint8_t* buf, uint16_t val) { memcpy(buf, &val, sizeof(val)); }
static inline void ByteWrite4(uint8_t* buf, uint32_t val) { memcpy(buf, &val, sizeof(val)); }

static inline uint32_t MsgHdrGetLength(const uint8_t* hdr_data) { return ByteRead4(hdr_data);}
static inline uint16_t MsgHdrGetStartCode(const uint8_t* hdr_data) { return ByteRead2(hdr_data + XMSG_FIELD_OFFSET_STARTCODE); }
static inline uint8_t MsgHdrGetFlags(const uint8_t* hdr_data) { return hdr_data[XMSG_FIELD_OFFSET_FLAGS]; }
static inline uint8_t MsgHdrGetType(const uint8_t* hdr_data) { return hdr_data[XMSG_FIELD_OFFSET_TYPE]; }
static inline uint8_t MsgHdrGetProtoVer(const uint8_t* hdr_data) { return XMSG_GET_PROTOCOL_VER(hdr_data[XMSG_FIELD_OFFSET_FLAGS]); }
static inline uint16_t MsgHdrGetMsgId(const uint8_t* hdr_data) { return ByteRead2(hdr_data+ XMSG_FIELD_OFFSET_MSG_ID); }
static inline uint16_t MsgHdrGetSeqNum(const uint8_t* hdr_data) { return ByteRead2(hdr_data+ XMSG_FIELD_OFFSET_SEQ_NUM); }

static inline void MsgHdrSetLength(uint8_t* hdr_data, uint32_t length) { ByteWrite4(hdr_data, length); }
static inline void MsgHdrSetStartCode(uint8_t* hdr_data, uint16_t start_code) { ByteWrite2(hdr_data + XMSG_FIELD_OFFSET_STARTCODE, start_code); }
static inline void MsgHdrSetStartCode(uint8_t* hdr_data, uint8_t byte1, uint8_t byte2) { 
    hdr_data[XMSG_FIELD_OFFSET_STARTCODE] = byte1;
    hdr_data[XMSG_FIELD_OFFSET_STARTCODE + 1] = byte2; 
}

static inline void MsgHdrSetFlags(uint8_t* hdr_data, uint8_t flags) { hdr_data[XMSG_FIELD_OFFSET_FLAGS] = flags; }
static inline void MsgHdrSetProtoVer(uint8_t* hdr_data, uint8_t ver) { hdr_data[XMSG_FIELD_OFFSET_FLAGS] = hdr_data[XMSG_FIELD_OFFSET_FLAGS] | (ver & 0x0f); }
static inline void MsgHdrSetType(uint8_t* hdr_data, uint8_t type) { hdr_data[XMSG_FIELD_OFFSET_TYPE] = type; }
static inline void MsgHdrSetMsgId(uint8_t* hdr_data, uint16_t msg_id) { ByteWrite2(hdr_data+ XMSG_FIELD_OFFSET_MSG_ID, msg_id); }
static inline void MsgHdrSetSeqNum(uint8_t* hdr_data, uint16_t seq_num) { ByteWrite2(hdr_data+ XMSG_FIELD_OFFSET_SEQ_NUM, seq_num); }

static inline uint32_t GetHeaderLength(uint8_t flags) { return XMSG_FLAGS_TP_FLAG(flags) ? (XMSG_BASE_HDR_SIZE + XMSG_PKT_HDR_SIZE) : XMSG_BASE_HDR_SIZE; }
static inline uint32_t GetHeaderLength(const uint8_t* hdr_data) { return GetHeaderLength(MsgHdrGetFlags(hdr_data)); }
static inline bool IsTpMessage(uint8_t flags) { return XMSG_FLAGS_TP_FLAG(flags); }
static inline bool IsTpMessage(const uint8_t* hdr_data) { return XMSG_FLAGS_TP_FLAG(MsgHdrGetFlags(hdr_data)); }

bool CheckMessageHeader(const uint8_t* data, uint32_t size);
bool InitializeMessageHeader(uint8_t* data, uint32_t length, uint8_t flags);
bool InitializeMessageHeader(uint8_t* data, uint32_t length, uint8_t flags, uint8_t type);
bool InitializeMessageHeader(uint8_t* data, uint32_t length, uint8_t flags, uint8_t type, MessageId msg_id);
bool InitializeMessageHeader(uint8_t* data, uint32_t length, uint8_t flags, uint8_t type, MessageId msg_id, SequenceNumber seq_num);

/** for extension header */
static inline uint32_t MsgExtHdrGetPktIdx(const uint8_t* hdr_data) { return IsTpMessage(hdr_data) ? ByteRead4(hdr_data + XMSG_FIELD_OFFSET_PKT_IDX) : XMSG_PKT_IDX_INVALID; }
static inline uint32_t MsgExtHdrGetPktTotal(const uint8_t* hdr_data) { return IsTpMessage(hdr_data) ? ByteRead4(hdr_data + XMSG_FIELD_OFFSET_PKT_TOTAL) : 0; }
static inline void MsgExtHdrSetPktIdx(uint8_t* hdr_data, uint32_t idx) { if (IsTpMessage(hdr_data)){ ByteWrite4(hdr_data + XMSG_FIELD_OFFSET_PKT_IDX, idx);}}
static inline void MsgExtHdrSetPktTotal(uint8_t* hdr_data, uint32_t total) { if (IsTpMessage(hdr_data)){ ByteWrite4(hdr_data + XMSG_FIELD_OFFSET_PKT_TOTAL, total);}}

/** for XMessageType */
XMessageType XMsgGetResponseType(XMessageType request_type);
} //namespace XMessageHelper

////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////

/** message structure spec
 * | length(4) | start_code(2) | flags(1) | type(1) | message id(2) | sequence number(2) | extension(dynamic)| payload (dynamic)| 
 * 
 * Header Base: 12 byte
 * length(4): total message size = header size + payloaf size 
 * start code(2): @see XMSG_START_CODE
 * flags(1):  
 * - bit 0~3: the protocol version. range is: 1 ~ 15, now is 1
 * - bit 4~7: flags:
 *       - bit 7: tp flag: 
 *              - 1: indicat a big packet will sent to target by split to many segments(e.g communication with udp). 
 *              - 0: just a normal message
 *       - bit 4~6: reserved
 * type(1):  The type of message @see XMessageType
 * message id(2): The identify of a message
 * sequence number(2): The sequence identify of a message
 */

class XMessage;

using XMessageSp = std::shared_ptr<XMessage>;

class XMessage {
public:
    using XComIdArray = std::vector<XComId>;
    using XMessageList = std::vector<XMessageSp>;
public:
    XMessage(uint8_t* buf);
    virtual ~XMessage();
public: /** [mandatory] base header operation */
    virtual uint8_t* data(){ return msg_data_; }
    virtual uint32_t length() const{ return XMessageHelper::MsgHdrGetLength(msg_data_); }
    virtual uint16_t start_code() const{ return XMessageHelper::MsgHdrGetStartCode(msg_data_); }
    virtual uint8_t flags() const{ return XMessageHelper::MsgHdrGetFlags(msg_data_); }
    virtual uint8_t protocol_version() const{ return XMessageHelper::MsgHdrGetProtoVer(msg_data_); }
    virtual uint8_t type() const{ return XMessageHelper::MsgHdrGetType(msg_data_); }
    virtual MessageId message_id() const{ return XMessageHelper::MsgHdrGetMsgId(msg_data_); }
    virtual SequenceNumber seq_num() const{ return XMessageHelper::MsgHdrGetSeqNum(msg_data_); }

    virtual void set_type(uint8_t type){ XMessageHelper::MsgHdrSetType(msg_data_, type); }
    virtual void set_message_id(MessageId message_id){ XMessageHelper::MsgHdrSetMsgId(msg_data_, message_id); }
    virtual void set_seq_num(SequenceNumber seq_num){ XMessageHelper::MsgHdrSetSeqNum(msg_data_, seq_num); }
    virtual bool check_valid() { return XMessageHelper::CheckMessageHeader(msg_data_, length()); }

public: /** [optional] packet information operation, multi packet info for a big message (extension header) */
    virtual bool is_tp_message() const{ return XMessageHelper::IsTpMessage(flags()); }
    virtual uint32_t pkt_total() const{ return XMessageHelper::MsgExtHdrGetPktTotal(msg_data_); }
    virtual uint32_t pkt_idx() const{ return XMessageHelper::MsgExtHdrGetPktIdx(msg_data_); }

    virtual void set_pkt_total(uint32_t total){ XMessageHelper::MsgExtHdrSetPktTotal(msg_data_, total); }
    virtual void set_pkt_idx(uint32_t idx){ XMessageHelper::MsgExtHdrSetPktIdx(msg_data_, idx); }

public: /** payload operation */
    virtual uint8_t* payload() const { return msg_data_ + XMessageHelper::GetHeaderLength(msg_data_); }
    virtual uint32_t payload_size() const { return length() - XMessageHelper::GetHeaderLength(msg_data_); }

public:
    virtual XMessageList unpack(uint32_t pkt_size);
    virtual XMessageSp clone();

public: /** helper API for message send/receive */
    virtual XComId msg_src() const { return msg_src_; }
    virtual const XComIdArray& msg_dst() const { return msg_dst_; }

    virtual void set_msg_src(XComId src_id) { msg_src_ = src_id; }
    virtual void add_msg_dst(XComId dst_id) { msg_dst_.push_back(dst_id); }
    virtual void set_msg_dst(const XComIdArray& dst_ids) { msg_dst_ = dst_ids; }

public: /** Create XMessage */
    /**
     * payload: 
     * if != nullptr, then Create XMessage and copy payload data.
     * if == nullptr, just Create XMessage with 'payload_size'
    */
    static XMessageSp CreateMsg(uint8_t msg_type, uint32_t payload_size, const uint8_t* payload = nullptr);
    static XMessageSp CreateMsg(uint8_t msg_type, MessageId msg_id, uint32_t payload_size, const uint8_t* payload = nullptr);
    static XMessageSp CreateMsg(uint8_t msg_type, MessageId msg_id, SequenceNumber seq_num, uint32_t payload_size, const uint8_t* payload = nullptr);
    static XMessageSp CreateMsg(const uint8_t* hdr_data, uint32_t hdr_size);


    static XMessageSp CreateTpMsg(uint8_t msg_type, uint32_t payload_size, const uint8_t* payload = nullptr); 
    


    /**byte buffer data to 'XMessage' structure,
     * msg_data: will relase by XMessage destruction function
     */
    static XMessageSp BuildMsg(uint8_t* msg_data, uint32_t msg_length);
    static XMessageSp CopyMsg(const uint8_t* msg_data, uint32_t msg_length);

protected:
    uint8_t* msg_data_ = nullptr;
    XComId msg_src_ = XBUS_INVALID_XCOM_ID;
    XComIdArray msg_dst_;
};


using XMessageQueue = core::XQueue<XMessageSp>;
using XMessageQueueSp = std::shared_ptr<XMessageQueue>;


inline XMessageQueueSp XMessageQueueCreate(uint32_t max_limit = 0){  /** if set as 0, inidicate unlimited this size */
    return std::make_shared<XMessageQueue>(max_limit);
}


} //namespace shmipc::com

#endif /** !__SHMIPC_COM_XMESSAGE_H__ */
